# 导入所需的库
import uuid
from contextlib import asynccontextmanager
from datetime import datetime
from datetime import timedelta
from typing import List, Any
from typing import Optional
from typing import Union
from PIL import Image
from io import BytesIO
import aiomysql
import oss2
import requests
import uvicorn
from fastapi import FastAPI, Depends, HTTPException, status, File, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.security import OAuth2PasswordBearer
from jose import jwt, JWTError
from pydantic import BaseModel, Field

# 高德地图的apikey 地址是:https://lbs.amap.com/api/webservice/guide/api/georegeo
api_key = '5b8c00aadfcd264625b2c3364c72ef1c'
# 创建 FastAPI 应用实例
app = FastAPI()

# 配置 CORS（跨源资源分享）
origins = ["*"]  # 允许所有来源的请求
methods = ["GET", "POST", "PUT", "DELETE"]  # 允许的 HTTP 方法
allow_credentials = True  # 允许发送身份验证信息（cookies）到服务器

# 添加 CORS 中间件到 FastAPI 应用
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,  # 允许的所有来源
    allow_credentials=allow_credentials,  # 允许发送身份验证信息
    allow_methods=methods,  # 允许的所有 HTTP 方法
    allow_headers=["*"],  # 允许的所有头部信息
)


# 配置数据库连接
@asynccontextmanager
async def get_db():
    conn = await aiomysql.connect(
        host="rm-2zea30h4sh8g15zd1ho.mysql.rds.aliyuncs.com",
        port=3306,
        user="root",
        password="Ds2024@()833429",
        db="qianrenqiandian"
    )
    try:
        yield conn
    finally:
        conn.close()


# 配置 OAuth2 密码流
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")  # 定义获取令牌的 URL

# 定义 JWT 认证的密钥和算法
SECRET_KEY = "key"  # JWT 密钥
ALGORITHM = "HS256"  # JWT 算法


# 定义用户模型
class User(BaseModel):
    id: Optional[int] = None  # int primary key auto_increment - 用户ID
    username: Optional[str] = None  # 用户名
    password: Optional[str] = None  # 密码
    nickname: Optional[str] = None  # 花名


# 定义数据库中的用户模型
class UserInDB(User):
    id: Optional[int] = None  # 用户 ID


# 定义注册用户模型
class UserRegister(BaseModel):
    username: str  # 用户名
    password: str  # 密码
    nickname: str  # 花名


# 定义地址模型
class AddressItem(BaseModel):
    id: Optional[int] = None  # 用户id
    longitude: Optional[Union[str, float]] = None  # 经度
    latitude: Optional[Union[str, float]] = None  # 纬度
    keyword: Optional[str] = None  # 搜索关键字


# 店铺信息表
class StoreInfoItem(BaseModel):
    id: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 店铺ID
    sales_id: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 业务员ID
    store_images: Optional[Union[List[Any], Any, None]] = Field(default_factory=lambda: None)  # 照片列表
    store_image_1: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 门店图片1
    store_image_2: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 门店图片2
    store_image_3: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 门店图片3
    longitude: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 经度
    latitude: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 纬度
    province: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 省
    city: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 市
    district: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 区
    township: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 街道
    storeName: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 门店名称
    storeAddress: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 门店地址
    responsibleName: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 负责人姓名
    storeArea: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 门店面积
    coachNumber: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 教练人数
    memberNumber: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 会员数量
    cardFee: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 卡费
    coachDailyCourses: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 教练日均课数
    competitors: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 竞品情况
    purchaseChannel: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 购买渠道
    cooperationIntention: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 合作意向
    other: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 其他
    nextPlan: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 下一步计划
    updaterId: Optional[Union[Any, None]] = Field(default_factory=lambda: None)  # 更新人ID


# JWT 认证函数，用于创建访问令牌
def create_access_token(data: dict, expires_delta: timedelta):
    to_encode = data.copy()  # 复制数据字典
    expire = datetime.utcnow() + expires_delta  # 计算过期时间
    to_encode.update({"exp": expire})  # 将过期时间添加到数据字典中
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)  # 使用密钥和算法编码数据字典为 JWT 令牌
    return encoded_jwt  # 返回编码后的 JWT 令牌


# 获取当前登录用户函数
def get_current_user(token: str = Depends(oauth2_scheme)):
    # 定义认证异常
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,  # 状态码 401 未授权
        detail="Could not validate credentials",  # 详细信息
        headers={"WWW-Authenticate": "Bearer"},  # 认证头部信息
    )
    try:
        # 解码 JWT 令牌
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")  # 从 payload 中提取用户名
        if username is None:  # 如果用户名不存在，抛出认证异常
            raise credentials_exception
    except JWTError:  # 如果解码失败，抛出认证异常
        raise credentials_exception

    # 从数据库中获取用户信息
    user = get_user(username)
    if user is None:  # 如果用户不存在，抛出认证异常
        raise credentials_exception

    return user  # 返回用户信息


# 从数据库中获取用户信息的函数
async def get_user(username: str) -> UserInDB:
    async with get_db() as conn:
        async with conn.cursor() as cursor:
            query = "SELECT id, username, password,nickname FROM users WHERE username = %s"
            await cursor.execute(query, (username,))
            result = await cursor.fetchone()
            if result:
                return UserInDB(id=result[0], username=result[1], password=result[2], nickname=result[3])


# 注册接口
@app.post("/register")
async def register(user: UserRegister):
    async with get_db() as db:
        async with db.cursor() as cursor:
            query = "INSERT INTO users (username, password,nickname) VALUES (%s, %s, %s)"  # 插入语句
            try:
                # 执行插入操作
                await cursor.execute(query, (user.username, user.password, user.nickname))
                await db.commit()
            except aiomysql.IntegrityError as e:
                if e.args[0] == 1062:  # Duplicate entry
                    raise HTTPException(status_code=400, detail="Username already registered")
                else:
                    raise HTTPException(status_code=500, detail="Database error")
            except Exception as e:
                raise HTTPException(status_code=500, detail=str(e))
    return {"username": user.username, "password": user.password, "nickname": user.nickname}


# 登录接口
@app.post("/login")
async def login(user: User):
    print('接到了登录请求')
    db_user = await get_user(user.username)
    if db_user is None or db_user.password != user.password:
        raise HTTPException(status_code=400, detail="Incorrect username or password")
    # 加密
    access_token_expires = timedelta(minutes=30)
    access_token = create_access_token(
        data={"sub": user.username}, expires_delta=access_token_expires
    )

    return {
        "access_token": access_token,
        "token_type": "bearer",
        "user_id": db_user.id,
        "nickname": db_user.nickname
    }


access_key_id = 'LTAI5tNtgEjQiVNRAXRbwzQK'
access_key_secret = 'EcJ7lpXAc6tOPcPoqeWQJfj1VzEsZo'
endpoint = 'oss-rg-china-mainland.aliyuncs.com'
bucket_name = 'qianrenqiandian'

auth = oss2.Auth(access_key_id, access_key_secret)
bucket = oss2.Bucket(auth, endpoint, bucket_name)


# 查询用户接口
@app.post("/query_user")
async def query_user(user: User):
    async with get_db() as db:
        async with db.cursor() as cursor:
            query = "select username,nickname from users where id=%s"  # 插入语句
            try:
                # 执行查询操作
                await cursor.execute(query, (user.id,))
                result = await cursor.fetchone()
                if not result:
                    raise HTTPException(status_code=404, detail="User not found")
            except aiomysql.IntegrityError as e:
                raise HTTPException(status_code=500, detail="Database error: " + str(e))
            except Exception as e:
                raise HTTPException(status_code=500, detail=str(e))
    return {
        "username": result['username'],
        "nickname": result['nickname']
    }


# 上传图片照片接口
@app.post("/upload_photos")
async def upload_photos(files: List[UploadFile] = File(...)):
    filenames = []
    file_urls = []
    for file in files:
        content = await file.read()
        image = Image.open(BytesIO(content))

        # 调整图片尺寸
        max_size = (800, 800)  # 设置最大尺寸
        image.thumbnail(max_size)

        # 压缩图片
        output = BytesIO()
        image.save(output, format='JPEG', quality=85)  # 调整质量参数以控制文件大小
        compressed_content = output.getvalue()

        unique_id = uuid.uuid4()
        object_key = f"{unique_id}.jpg"
        bucket.put_object(object_key, compressed_content)
        file_url = f"https://{bucket_name}.{endpoint}/{object_key}"
        filenames.append(file.filename)
        file_urls.append(file_url)
    return JSONResponse(content={"filenames": filenames, "file_urls": file_urls})

# 定义一个函数，将空字符串转换为 None
def convert_empty_to_none(value):
    return value if value != '' else None


# 添加店铺信息接口
@app.post("/add_store")
# 为了提高开发效率,这里不进行令牌校验了
async def add_store(storeInfoItem: StoreInfoItem):
    print('接到了前端的请求')
    store_image_1, store_image_2, store_image_3 = None, None, None
    async with get_db() as db:
        async with db.cursor() as cursor:  # 使用异步上下文管理器创建游标
            if storeInfoItem.store_images and len(storeInfoItem.store_images) > 0:
                for index, store_image in enumerate(storeInfoItem.store_images):
                    if index == 0:
                        store_image_1 = store_image
                    elif index == 1:
                        store_image_2 = store_image
                    elif index == 2:
                        store_image_3 = store_image
            insert_sql = """INSERT INTO 店铺信息表 
            (业务员id, 门店图片1, 门店图片2, 门店图片3, 经度, 纬度, 省, 市, 区, 门店名称, 门店地址, 负责人姓名, 门店面积, 教练人数, 会员数量, 卡费, 教练日均课数, 竞品情况, 购买渠道, 合作意向, 其他, 下一步计划,更新人id) 
            VALUES
             (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,%s,%s, %s)
                """
            values = (
                storeInfoItem.sales_id,  # 业务员id
                store_image_1,  # 门店图片1
                store_image_2,  # 门店图片2
                store_image_3,  # 门店图片3
                convert_empty_to_none(storeInfoItem.longitude),  # 经度
                convert_empty_to_none(storeInfoItem.latitude),  # 纬度
                convert_empty_to_none(storeInfoItem.province),  # 省
                convert_empty_to_none(storeInfoItem.city),  # 市
                convert_empty_to_none(storeInfoItem.district),  # 区
                convert_empty_to_none(storeInfoItem.storeName),  # 门店名称
                convert_empty_to_none(storeInfoItem.storeAddress),  # 门店地址
                convert_empty_to_none(storeInfoItem.responsibleName),  # 负责人姓名
                convert_empty_to_none(storeInfoItem.storeArea),  # 门店面积
                convert_empty_to_none(storeInfoItem.coachNumber),  # 教练人数
                convert_empty_to_none(storeInfoItem.memberNumber),  # 会员数量
                convert_empty_to_none(storeInfoItem.cardFee),  # 卡费
                convert_empty_to_none(storeInfoItem.coachDailyCourses),  # 教练日均课数
                convert_empty_to_none(storeInfoItem.competitors),  # 竞品情况
                convert_empty_to_none(storeInfoItem.purchaseChannel),  # 购买渠道
                convert_empty_to_none(storeInfoItem.cooperationIntention),  # 合作意向
                convert_empty_to_none(storeInfoItem.other),  # 其他
                convert_empty_to_none(storeInfoItem.nextPlan),  # 下一步计划
                storeInfoItem.updaterId  # 更新人id
            )
            log_sql = insert_sql % values
            print(log_sql)
            try:
                await cursor.execute(insert_sql, values)
                await db.commit()  # 提交事务
            except Exception as e:  # 如果插入失败，抛出 HTTP 500 错误并返回异常信息
                print(e)
                raise HTTPException(status_code=500, detail=str(e))

    return {"message": "Data added successfully"}  # 返回数据添加成功的消息


# 查询本人店铺信息接口
@app.post("/query_store")
async def query_store(storeInfoItem: StoreInfoItem):
    async with get_db() as db:
        async with db.cursor(aiomysql.DictCursor) as cursor:  # 使用字典游标
            query_sql = """
            SELECT 门店图片1, 门店图片2, 门店图片3, 经度, 纬度, 省, 市, 区,街道, 门店名称, 门店地址, 
                   负责人姓名, 门店面积, 教练人数, 会员数量, 卡费, 教练日均课数, 竞品情况, 
                   购买渠道, 合作意向, 其他, 下一步计划, 创建时间, 更新人id, 更新时间
            FROM 店铺信息表 
            WHERE 业务员id = %s
            """
            try:
                await cursor.execute(query_sql, (storeInfoItem.sales_id,))
                results = await cursor.fetchall()
            except Exception as e:
                raise HTTPException(status_code=500, detail=str(e))
    return {"data": results}  # 返回实际的查询数据


# 查询全部店铺信息接口
@app.post("/query_store_all")
async def query_store_all(storeInfoItem: StoreInfoItem):
    if storeInfoItem.sales_id > 10:
        return {"data": []}
    async with get_db() as db:
        async with db.cursor(aiomysql.DictCursor) as cursor:  # 使用字典游标
            query_sql = """
            SELECT nickname,门店图片1, 门店图片2, 门店图片3, 经度, 纬度, 省, 市, 区, 门店名称, 门店地址, 
                   负责人姓名, 门店面积, 教练人数, 会员数量, 卡费, 教练日均课数, 竞品情况, 
                   购买渠道, 合作意向, 其他, 下一步计划, 创建时间, 更新人id, 更新时间
            FROM 店铺信息表 a join users on users.id=a.业务员id
            """
            try:
                await cursor.execute(query_sql)
                results = await cursor.fetchall()
            except Exception as e:
                raise HTTPException(status_code=500, detail=str(e))

    return {"data": results}  # 返回实际的查询数据


# 获取店铺地址接口
# 高德地图api地址:
@app.post("/get_address_by_coordinates")
# 令牌校验后续再做
# async def get_address_by_coordinates(addressItem: AddressItem, current_user: dict = Depends(get_current_user)):
async def get_address_by_coordinates(addressItem: AddressItem):
    if not addressItem.id or not addressItem.longitude or not addressItem.latitude or not addressItem.keyword:
        print("缺少参数")
        return
    parameters = {
        'key': api_key,
        'location': f'{addressItem.longitude},{addressItem.latitude}',
        'radius': 1000,
        'keywords': addressItem.keyword,
        'page_size': 25
    }
    url = f"https://restapi.amap.com/v5/place/around?parameters"
    response = requests.get(url, params=parameters)
    if response.status_code == 200:
        data = response.json()
        data_count = int(data['count'])
        if data['status'] == '1' and data_count > 0:
            addresses = []
            for poi in data['pois']:
                location = poi['location']
                results = location.split(',')
                longitude = results[0]
                latitude = results[1]
                address_info = {
                    'province': poi['pname'],  # 省
                    'city': poi['cityname'],  # 市
                    'district': poi['adname'],  # 区
                    'address': poi['address'],  # 地址
                    'name': poi['name'],  # 名称
                    'longitude': longitude,  # 经度
                    'latitude': latitude  # 纬度
                }
                addresses.append(address_info)
            return addresses
        else:
            print(f"Error: {data['info']}")
    else:
        print(f"Request failed with status code: {response.status_code}")
    return None


if __name__ == "__main__":
    # uvicorn.run("qianrenqiandian:app", host="0.0.0.0", port=8004, workers=6)
    uvicorn.run("qianrenqiandian:app", host="0.0.0.0", port=8004, workers=4, ssl_keyfile="nginx.key",
                ssl_certfile="nginx.crt")

# uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4 提升工作能力
# https://aimk.chinacpt.com:8004
